Plugin API

Plugin API

Введение

Plugin API позволяет разработчикам создавать собственные плагины (драйверы) для интеграции новых устройств с системой BARY. Плагины работают как отдельные процессы и взаимодействуют с основным приложением через IPC (Inter-Process Communication).

Возможности:

  • Поддержка любых IoT устройств и протоколов
  • Изоляция драйверов в отдельных процессах
  • Автоматическая установка зависимостей
  • Логирование и мониторинг
  • Публикация событий в реальном времени
  • Mixins — модульное расширение функциональности
  • Templates — конфигурационные файлы устройств
  • Динамические capabilities — генерация интерфейса в runtime
  • Settings_ex — динамические настройки с фильтрацией

Установка и структура проекта

Готовый шаблон

Для быстрого старта используйте готовый пустой шаблон плагина:

GitHub репозиторий: https://github.com/gapaus/bary_plugin_empty

Этот шаблон содержит всю необходимую структуру, конфигурационные файлы, базовые классы и инструменты для разработки плагинов BARY.

Базовая структура плагина

my-plugin/
├── package.json          # Зависимости и метаданные
├── tsconfig.json         # Конфигурация TypeScript
├── webpack.config.js     # Сборка плагина
├── debug.sh              # Скрипт отладки
├── nodemon.json          # Конфигурация dev-режима
├── src/
│   ├── mixins/           # Пользовательские миксины (опционально)
│   │   ├── params.ts
│   │   └── ipc.ts
│   ├── my-plugin.ts      # Основной файл плагина
│   └── my-plugin.json    # Метаданные плагина для BARY
├── core/                 # Базовые классы (наследуются)
│   ├── base-module.ts
│   └── base-driver-module.ts
├── enums/
│   └── EventTypes.ts     # Типы событий
├── lib/                  # Вспомогательные библиотеки
│   ├── better-queue/     # Система очередей
│   ├── foibles/          # Система миксинов
│   ├── require-ex.ts     # Автоматическая установка зависимостей
│   └── shared.functions.ts
├── templates/            # Конфигурационные файлы устройств (опционально)
│   └── device-config.json
└── dist/                 # Скомпилированные файлы

package.json

{
  "name": "bary-plugin-mydevice",
  "version": "1.0.0",
  "main": "src/my-plugin.ts",
  "scripts": {
    "build": "webpack --display_modules",
    "debug": "nodemon --config nodemon.json"
  },
  "dependencies": {
    "async-mutex": "^0.5.0",
    "moment": "^2.27.0",
    "node-ipc": "^9.1.1",
    "winston": "^3.17.0",
    "uuid": "^8.3.2"
  },
  "devDependencies": {
    "@types/node": "^14.14.34",
    "ts-loader": "^8.0.14",
    "ts-node": "^10.3.0",
    "typescript": "^4.1.3",
    "webpack": "^4.46.0",
    "webpack-cli": "^3.2.3"
  }
}

my-plugin.json

Метаданные и конфигурация плагина для BARY:

{
  "name": "My Device Plugin",
  "type": 1,
  "icon": "hub",
  "class_name": "my-plugin",
  "module": "my-plugin",
  "manufacturer": "Company Name",
  "cloud": false,
  "support_autoupdate": false,
  "dynamic_capabilities": true,
  "mac_address": false,
  "capabilities": [],
  "sort_index": 4,
  "sub_devices": [
    {
      "class_name": "my-plugin.subdevice",
      "name": "My SubDevice",
      "dynamic_capabilities": true,
      "mac_address": false,
      "support_autoupdate": true,
      "autoupdate_interval": 500,
      "selectable": true,
      "connect_config": true,
      "type": 39,
      "sort_index": 1,
      "settings": [
        {
          "key": "identifier",
          "name": "Device ID",
          "type": "text",
          "required": true
        }
      ],
      "commands": [
        {
          "command": "reset"
        }
      ]
    }
  ],
  "settings": [
    {
      "key": "port",
      "name": "Port",
      "type": "text",
      "required": true,
      "defaultValue": "/dev/ttyUSB0"
    },
    {
      "key": "baud_rate",
      "name": "Baud rate",
      "type": "select",
      "items": [
        {"id": 9600, "title": "9600"},
        {"id": 19200, "title": "19200"},
        {"id": 115200, "title": "115200"}
      ],
      "defaultValue": 9600
    },
    {
      "key": "enable_logging",
      "name": "Enable logging",
      "type": "checkbox"
    }
  ],
  "commands": [
    {
      "command": "scan_devices"
    },
    {
      "command": "reset"
    }
  ],
  "display": [
    {
      "functions": [
        {
          "title": "Scan Mode",
          "type": "scan_mode",
          "view": "buttons",
          "model": "device.:ident.status.scan_mode",
          "buttons": [
            {"value": "auto", "title": "Automatic"},
            {"value": "manual", "title": "Manual"}
          ],
          "data": {
            "ident": ":ident",
            "command": "scan_mode",
            "value": ":scan_mode"
          }
        }
      ]
    }
  ],
  "dependencies": {
    "my-device-sdk": "^2.0.0"
  }
}

Основные поля:

  • name — отображаемое имя плагина
  • type — уникальный тип устройства (число)
  • icon — иконка плагина
  • class_name — техническое имя класса
  • module — имя модуля
  • manufacturer — производитель (опционально)
  • cloud — поддержка облака
  • support_autoupdate — автообновление статуса
  • dynamic_capabilities — capabilities генерируются в runtime
  • mac_address — требуется MAC-адрес
  • disable_display_sort — отключить сортировку отображения
  • capabilities — статические возможности устройства
  • sub_devices — конфигурация дочерних устройств
  • settings — статические настройки плагина
  • commands — команды плагина
  • display — кастомизация UI (опционально)
  • dependencies — NPM-зависимости (устанавливаются автоматически)

Базовые классы

baseModule

Базовый класс для всех плагинов, обеспечивает IPC коммуникацию с BARY.

Основные свойства:

  • config — конфигурация приложения
  • ipc — IPC клиент для связи с BARY
  • events — массив зарегистрированных событий
  • requestId — счетчик запросов
  • external_driver — флаг внешнего драйвера

Основные методы:

// Отправка запроса с ожиданием ответа
request(eventName: string, params: object): Promise<any>

// Отправка запроса без ожидания ответа
requestEx(eventName: string, params: object): void

// Логирование
log(message: any): void
error(message: any): void

// Динамическая загрузка npm модулей
require(ident: string, require: boolean): Promise<any>

baseDriverModule

Расширяет baseModule дополнительными методами для работы с драйверами устройств.

Дополнительные свойства:

  • device_id — ID устройства
  • params — параметры устройства
  • config — конфигурация устройства
  • logger — Winston logger для записи логов
  • ident — идентификатор устройства
  • appDevices — список устройств приложения
  • statusCache — кэш статусов для оптимизации
  • pluginTemplate — метаданные плагина из JSON

Жизненный цикл:

// 1. Инициализация плагина
initDeviceEx(resolve, reject): void

// 2. Подключение к устройству
connectEx(resolve, reject): void

// 3. Обработка команд
commandEx(command, value, params, options, resolve, reject, status): void

// 4. Получение списка дочерних устройств
getSubDevicesEx(resolve, reject, zones): void

// 5. Обработка событий от подписанных устройств (опционально)
onSubscribeDevice(params: any): void

Дополнительные методы:

// Создание/обновление дочернего устройства
checkSubDevice(model, key, name, params, zone_id): Promise<any>

// Публикация событий
publish(eventType, ...params): void
publishStatus(eventType, status): void

// Формирование имени события для статуса
eventTypeStatus(className, identifier?, key?): string

// Подписка на события другого устройства
subscribeDevice(ident, eventType): void

// Уведомления
sendNotify(message, options?): void
sendNotifyEx(body): void
sendPushNotification(message, email, title): void

// Работа с устройствами
getDevices(params?): Promise<Device[]>
deviceCommand(ident, command, data, value): Promise<any>
deviceEvent(ident, event, data): Promise<any>

// Работа с БД
getTable(table, options): Promise<any[]>
createTable(table, options): Promise<any>
updateTable(table, options, where): Promise<any>
getConfig(): Promise<any>
getDeviceCustomData(ident, custom_data_id): Promise<any>

// Загрузка templates
loadTemplate(ident, name, options?): any

// Очереди
startQueue(ident): void
countQueue(ident): void
doneQueue(ident, resolve, reject, inc?, error?): void

Расширение функциональности через Mixins

Что такое Mixins?

BARY использует библиотеку foibles для создания миксинов — модульного способа расширения функциональности классов.

Создание миксина

// src/mixins/params.ts
const {Mixin} = require('../../lib/foibles');

export const DEVICE_ID_INC = 10000;

export const Params = Mixin(parent => class Params extends parent {
  
  capabilities = [];
  settings_ex = [];
  
  get heater_list() {
    return this.params?.heater_list || [];
  }
  
  get heater_list_devices() {
    return this.heater_list 
      ? this.appDevices.filter(item => 
          this.heater_list.find(item1 => item1 === item.ident)
        ) 
      : [];
  }
  
  getDeviceParam(params, device, key, defaultValue) {
    return params[`${device.ident}_${key}`] !== undefined 
      ? params[`${device.ident}_${key}`] 
      : defaultValue;
  }
  
  updateCapabilities() {
    this.capabilities = [];
    
    this.heater_list_devices.forEach(device => {
      this.capabilities.push({
        ident: 'power',
        index: DEVICE_ID_INC + device.id,
        display_name: `${device.name} (${device.zone_name})`,
        options: {
          info: `info_${DEVICE_ID_INC + device.id}`,
          ignore_changes: true
        }
      });
    });
  }
  
});

Использование миксинов

import {baseDriverModule} from '../core/base-driver-module';
import {Params, DEVICE_ID_INC} from './mixins/params';
import {IPC} from './mixins/ipc';

// Расширение класса несколькими миксинами
class MyPlugin extends baseDriverModule.with(Params, IPC) {
  
  connectEx(resolve, reject) {
    this.getDevices({currentStatus: true}).then(devices => {
      this.appDevices = devices;
      
      // Использование методов из миксина Params
      this.updateCapabilities();
      
      // Доступ к геттерам миксина
      this.heater_list_devices.forEach(device => {
        console.log(device.name);
      });
      
      // Публикация capabilities
      this.publish(
        this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
        {capabilities: this.capabilities, settings_ex: this.settings_ex}
      );
      
      resolve({});
    });
  }
  
}

const app = new MyPlugin();

Templates — конфигурационные файлы устройств

Создание templates

Плагины могут хранить конфигурационные файлы в папке /templates/:

my-plugin/
└── templates/
    ├── device1.json
    ├── device2.json
    └── conditioner.json

Пример template

{
  "manufacturer": "Philio Technology Corp",
  "manufacturerId": "0x013c",
  "label": "PAR01",
  "description": "Conditioner",
  "devices": [
    {
      "productType": "0x0106",
      "productId": "0x8290"
    }
  ],
  "firmwareVersion": {
    "min": "0.0",
    "max": "255.255"
  },
  "paramInformation": [
    {
      "#": "25",
      "label": "Learn mode",
      "valueSize": 2,
      "minValue": 0,
      "maxValue": 2048,
      "defaultValue": 1
    },
    {
      "#": "27",
      "label": "Temperature",
      "valueSize": 2,
      "minValue": 16,
      "maxValue": 30,
      "defaultValue": 22
    }
  ]
}

Загрузка template

// В методе плагина
const template = this.loadTemplate('my-plugin', 'conditioner.json');
console.log(template.manufacturer); // "Philio Technology Corp"

// С опциями (если template — это функция)
const template = this.loadTemplate('my-plugin', 'device-template', {
  deviceId: 123
});

Создание плагина

Минимальный пример

import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';

class MyPlugin extends baseDriverModule {

  /**
   * Инициализация плагина
   */
  initDeviceEx(resolve, reject) {
    super.initDeviceEx(() => {
      this.app.log('Плагин инициализирован');
      
      // Создание имени события для статуса
      this.eventName = this.eventTypeStatus(
        this.pluginTemplate.class_name, 
        this.id
      );
      
      resolve({});
    }, reject);
  }

  /**
   * Подключение к устройству
   */
  connectEx(resolve, reject) {
    this.app.log('Подключение к устройству...');
    
    // Получение списка устройств
    this.getDevices({currentStatus: true}).then(devices => {
      this.appDevices = devices;
      
      // Подписка на события устройств
      this.appDevices.forEach(device => {
        this.subscribeDevice(device.ident, 'changed_power');
      });
      
      // Публикация динамических capabilities
      const capabilities = [];
      capabilities.push({
        ident: 'power',
        index: 1,
        display_name: 'Включить/выключить',
        options: {
          hideTitle: false
        }
      });
      
      this.publish(this.eventName, {capabilities});
    });
    
    resolve({});
  }

  /**
   * Обработка команд
   */
  commandEx(command, value, params, options, resolve, reject, status) {
    this.app.log('Команда:', command, 'Значение:', value);
    
    // Проверка прав администратора (если нужно)
    if (command === 'admin_action' && options && !options.is_admin) {
      return reject({message: 'Access Denied!'});
    }
    
    switch(command) {
      case 'on':
        // Включить устройство
        resolve({state: 'on'});
        break;
      case 'off':
        // Выключить устройство
        resolve({state: 'off'});
        break;
      case 'status':
        // Получить статус
        resolve({connected: true, state: 'on'});
        break;
      default:
        reject({message: 'Неизвестная команда'});
    }
  }
  
  /**
   * Обработка событий от подписанных устройств
   */
  onSubscribeDevice(params: any) {
    const device = this.appDevices.find(item => item.ident === params.ident);
    if (device) {
      device.currentStatus = params.currentStatus;
      // Реакция на изменение статуса
      this.handleDeviceUpdate(device);
    }
  }
}

// Создание экземпляра плагина
const app = new MyPlugin();
app.logging = true;

Динамические Capabilities

Capabilities можно генерировать динамически в методе connectEx() или при изменении конфигурации.

Публикация capabilities

connectEx(resolve, reject) {
  const capabilities = [];
  
  // Простой переключатель
  capabilities.push({
    ident: 'power',
    index: 1,
    display_name: 'Управление отоплением',
    options: {
      hideTitle: false,
      hideSeparator: false,
      hideBottomPadding: false,
      ignore_changes: false,
      read_only: false
    }
  });
  
  // Текстовое поле с измерением
  capabilities.push({
    ident: 'text',
    index: 2,
    display_name: 'Температура',
    options: {
      measure: '°C',
      read_only: true
    }
  });
  
  // Ползунок (range)
  capabilities.push({
    ident: 'target_temperature',
    index: 3,
    display_name: 'Желаемая температура',
    options: {
      minValue: 5,
      maxValue: 30,
      stepValue: 0.5,
      hideTitle: true
    }
  });
  
  // Capability с информационным полем
  capabilities.push({
    ident: 'power',
    index: 10,
    display_name: 'Насос 1',
    options: {
      info: 'info_10',        // Связано с status.info_10
      ignore_changes: true
    }
  });
  
  // Заголовок
  capabilities.push({
    ident: 'title',
    index: 100,
    options: {
      title_only: 'Климат-контроль'
    },
    hide_background: true,
    hide_bottom_padding: true,
    hide_separator: true
  });
  
  // Capability для добавления/удаления устройств
  capabilities.push({
    ident: 'power',
    index: 1,
    display_name: 'Add Z-Wave device',
    options: {
      link_devices: true
    }
  });
  
  capabilities.push({
    ident: 'power',
    index: 2,
    display_name: 'Remove Z-Wave device',
    options: {
      unlink_devices: true
    }
  });
  
  // Публикация
  this.publish(
    this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
    {capabilities}
  );
  
  resolve({});
}

Доступные опции capabilities

  • hideTitle — скрыть заголовок
  • hideSeparator — скрыть разделитель
  • hideBottomPadding — скрыть отступ снизу
  • ignore_changes — игнорировать изменения
  • read_only — только для чтения
  • info — связь с информационным полем (например, info_10 связано с status.info_10)
  • measure — единица измерения (например, °C, %, W)
  • minValue, maxValue, stepValue — для ползунков (range)
  • value_on, value_off — значения для переключателей
  • title_only — только заголовок без фона (для ident: ‘title’)
  • link_devices, unlink_devices — для добавления/удаления устройств

Динамические Settings (settings_ex)

Settings_ex позволяют создавать настройки с фильтрацией устройств по командам и типам.

Публикация settings_ex

connectEx(resolve, reject) {
  const settings_ex = [];
  
  // Выбор устройства с фильтрацией по командам
  settings_ex.push({
    key: 'temperature_sensor',
    name: 'Датчик температуры',
    type: 'select',
    items: [],
    driver_support: [                    // Фильтр по поддерживаемым командам
      'supportTemperature',
      'supportTemperatureEx'
    ],
    multi: false                         // Одиночный выбор
  });
  
  // Множественный выбор устройств
  settings_ex.push({
    key: 'heater_list',
    name: 'Список обогревателей',
    type: 'select',
    items: [],
    driver_support: ['supportPower', 'supportPowerEx'],
    driver_types: [5],                   // Фильтр по типу драйвера
    multi: true                          // Множественный выбор
  });
  
  // Выбор комнаты
  settings_ex.push({
    key: 'zone_id',
    name: 'Комната',
    type: 'select',
    items: [],
    zone_support: ['rooms']              // Показывать только комнаты
  });
  
  // Условная видимость
  settings_ex.push({
    key: 'advanced_options',
    name: 'Дополнительные опции',
    type: 'text',
    visibleField: 'enable_advanced',     // Показывать только если
    visibleFieldValue: true              // enable_advanced = true
  });
  
  // Текстовое поле с значением по умолчанию
  settings_ex.push({
    key: 'interval',
    name: 'Интервал обновления (мин)',
    type: 'text',
    defaultValue: 5
  });
  
  // Заголовок (группировка настроек)
  settings_ex.push({
    key: 'group_heating',
    name: 'Настройки отопления',
    type: 'title'
  });
  
  // Динамическое создание настроек для каждого устройства
  this.heater_list_devices.forEach(device => {
    settings_ex.push({
      key: `${device.ident}_kp`,
      name: `${device.name} - Пропорциональная составляющая`,
      type: 'text',
      defaultValue: 0.5
    });
  });
  
  // Публикация вместе с capabilities
  this.publish(
    this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
    {capabilities, settings_ex}
  );
  
  resolve({});
}

Доступные опции settings_ex

  • key — ключ настройки
  • name — отображаемое имя
  • type — тип поля (text, select, checkbox, title)
  • items — элементы для select
  • defaultValue — значение по умолчанию
  • required — обязательное поле
  • driver_support — фильтрация устройств по поддерживаемым командам
  • driver_types — фильтрация устройств по типу
  • multi — множественный выбор (для select)
  • zone_support — фильтрация зон (['rooms'] — только комнаты)
  • visibleField — поле, от которого зависит видимость
  • visibleFieldValue — значение поля для отображения
  • description — описание настройки
  • group — группа настроек (например, settingsDevice)

API методы

Работа с устройствами

Создание дочернего устройства

checkSubDevice(
  model: string,      // Модель устройства (например, 'zigbee2mqtt.light')
  key: string,        // Уникальный ключ
  name: string,       // Название устройства
  params: object,     // Параметры устройства
  zone_id: number     // ID зоны (null для автоматического определения)
): Promise<any>

Параметры устройства:

{
  icon: 'light',           // Иконка устройства
  identifier: 'unique_id', // Уникальный идентификатор
  capabilities: [          // Возможности устройства
    {
      index: 0,
      property: 'state',
      ident: 'state',
      display_name: 'Состояние',
      access: 'rw',        // r=read, w=write, rw=read+write
      homekit: true,       // Поддержка HomeKit
      yandex: true,        // Поддержка Яндекс Алиса
      sber: true,          // Поддержка Сбер
      options: {
        value_on: true,
        value_off: false,
        minValue: 0,
        maxValue: 100,
        stepValue: 1
      }
    }
  ],
  settings_ex: [           // Динамические настройки устройства
    {
      key: 'parameter_1',
      name: 'Parameter 1',
      type: 'text',
      defaultValue: 10
    }
  ],
  parent_identifier: 123   // ID родительского устройства
}

Доступные иконки:

  • Освещение: light, chandelier, rgb_lamp, rgb_strip, rgb_led, table_lamp, spotlight, sconce, facade_light
  • Развлечения: tv, gamepad, music_note, speaker
  • Безопасность: camera, security, intercom, doorlock, night, smoke, gas_leak, leak
  • Дом: home, gates, gate1, gate2, gate3, gate4, socket, infrared, faucet, valve, pump, hub
  • Шторы/жалюзи: view_column, louvers, marquise
  • Климат: floor_heater, heater, heater1, convector, humidifier, ac_unit, fan, dryer, cooling, anti-icing
  • Датчики: co2, voc, p2, multisensor, temp_sensor, humidity_sensor, door_sensor, counter, motion
  • Бытовая техника: kettle, exhaust_hood, fridge, washing_machine, microwave, dish_washer, oven, stove, vacuum_cleaner, pool, squirt

Типы плагинов (driver_types):

BARY поддерживает 52 типа плагинов для различных устройств и сервисов:

  • Шлюзы и сервисы: Шлюзы (1), Облачные сервисы (2), Провайдеры услуг (19), Система (39), Внешние API для камеры (43), Музыкальные сервисы (45)
  • Медиа устройства: Игровые приставки (7), Ресиверы (8), Телевизоры (9), ТВ-приставки (18), Умные колонки (37), Интерактивные панели (50)
  • Умный дом базовый: Умные розетки (3), Выключатели (12), Релейные модули (14), Эмуляторы IR/RF (16), Умные лампочки (22), Освещение (25), RGB Контроллеры (40), Диммеры (44)
  • Климат-контроль: Термостаты (13), Кондиционеры (17), Системы вентиляции (33), Умные вентиляторы (41), Климат-контроль (47), Увлажнители (6)
  • Датчики: Датчики температуры (11), Датчики влажности (23), Датчики освещенности (24), Датчики протечки (26), Датчики движения (27), Датчики напряжения (28), Датчики УФ (29), Датчики открытия окон и дверей (30), Комбинированные датчики (31), Детекторы дыма (36), Мониторы качества воздуха (21)
  • Безопасность: Камеры (5), Дверные замки (32), Домофоны (42), Распознавание ГРЗ (48), СКУД (49)
  • Бытовая техника: Пылесосы (4), Приводы штор (38), Уход за растениями (20)
  • Инженерные системы: Счетчики электроэнергии (15), Водосчетчики (35), Умные контроллеры (34), Модули ввода-вывода (46), Модули управления устройствами (51)
  • Голосовое управление: Голосовые ассистенты (52), Погода (10)

Получение списка устройств

getDevices(params?: object): Promise<Device[]>

Пример:

const devices = await this.getDevices({currentStatus: true});
devices.forEach(device => {
  this.app.log('Устройство:', device.name, 'Статус:', device.currentStatus);
});

Подписка на события устройства

subscribeDevice(ident: string, eventType: string): void

Пример:

// Подписка на изменение питания
this.subscribeDevice('device-001', 'changed_power');

// Обработка в onSubscribeDevice()
onSubscribeDevice(params: any) {
  const device = this.appDevices.find(item => item.ident === params.ident);
  if (device) {
    device.currentStatus = params.currentStatus;
    this.app.log('Статус изменился:', device.name, params.currentStatus);
  }
}

Отправка команды другому устройству

deviceCommand(
  ident: string,    // Идентификатор устройства
  command: string,  // Команда
  data: object,     // Данные
  value: any        // Значение
): Promise<any>

Пример:

await this.deviceCommand('light_bedroom', 'on', {}, true);

Публикация событий

Формирование имени события

eventTypeStatus(
  className: string,
  identifier?: string,
  key?: string
): string

Пример:

const eventName = this.eventTypeStatus('my-plugin', 'device-001', 'temperature');
// Результат: "status->my-plugin->device-001->temperature"

// Для плагина
const eventName = this.eventTypeStatus(this.pluginTemplate.class_name, this.id);
// Результат: "status->my-plugin->driver-123"

Публикация статуса устройства

publishStatus(eventType: EventTypes, status: object): void

Пример:

// Отправка данных с датчика
this.publishStatus(EventTypes.UpdateTemperature, {
  temperature_living_room: 22.5,
  humidity_living_room: 45,
  connected: true
});

Оптимизация публикации:

Метод publishStatus автоматически:

  • Публикует только измененные данные
  • Отправляет числовые данные при изменении >10% или раз в 15 секунд
  • Всегда отправляет события датчиков движения, открытия и т.д.

Прямая публикация событий

publish(eventType: EventTypes | string, ...params: any[]): void

Пример:

this.publish(EventTypes.ChangedMotion, {
  parent_identifier: this.device_id,
  motion_sensor_1: true
});

// Публикация capabilities и settings_ex
this.publish(
  this.eventTypeStatus(this.pluginTemplate.class_name, this.id),
  {capabilities, settings_ex, displays: [...]}
);

Уведомления

Отправка уведомления пользователю

sendNotify(message: string, options?: object): void

Пример:

this.sendNotify('Устройство подключено успешно!');

Системные уведомления (sendNotifyEx)

sendNotifyEx(body: object): void

Пример:

// Обновление устройства
this.sendNotifyEx({
  system: true,
  type: 'device-update',
  ident: this.ident
});

// Обновление настроек
this.sendNotifyEx({
  system: true,
  type: 'settings-update',
  ident: this.ident
});

Push-уведомление

sendPushNotification(
  message: string,
  email: string,
  title: string
): void

Пример:

this.sendPushNotification(
  'Обнаружено движение в гостиной',
  'user@example.com',
  'Датчик движения'
);

Работа с данными

Получение конфигурации

getConfig(): Promise<any>

Работа с базой данных

// Получение записей
getTable(table: string, options: object): Promise<any[]>

// Создание записи
createTable(table: string, options: object): Promise<any>

// Обновление записей
updateTable(table: string, options: object, where: object): Promise<any>

Пример:

// Получение всех устройств типа 'light'
const lights = await this.getTable('devices', {
  where: {type: 'light'}
});

// Создание записи
await this.createTable('custom_data', {
  device_id: 123,
  key: 'last_seen',
  value: new Date().toISOString()
});

// Обновление
await this.updateTable(
  'devices',
  {name: 'Updated Name'},
  {where: {id: 123}}
);

Облачные запросы

cloudRequest(params: object): Promise<any>

Работа с очередями (Better-queue)

// Создание очереди запросов
const Queue = require('../lib/better-queue/queue');

this.requestQueue = new Queue((task, callback) => {
  this.deviceCommand(
    task.ident,
    task.command,
    {},
    task.value
  ).then(() => {
    callback(null, {});
  }).catch(error => {
    callback(error);
  });
}, {
  concurrent: 1,        // Последовательная обработка
  maxRetries: 3
});

// Добавление задачи в очередь
this.requestQueue.push({
  ident: 'device-001',
  command: 'set_power',
  value: true
}, (error, result) => {
  if (error) {
    console.error('Error:', error);
  } else {
    console.log('Success:', result);
  }
});

// Встроенные методы для простых очередей
this.startQueue('commands');
this.countQueue('commands');
this.doneQueue('commands', resolve, reject);

Типы событий (EventTypes)

Устройства

  • DeviceCreate — создание устройства
  • DeviceUpdate — обновление устройства
  • DeviceDelete — удаление устройства
  • DeviceConnect — подключение устройства
  • DeviceDiscover — обнаружение нового устройства

Обновления данных

  • UpdateTemperature — обновление температуры
  • UpdateThermostatTemp — обновление температуры термостата
  • UpdatePower — обновление мощности
  • UpdateAlert — предупреждение

События датчиков

  • ChangedMotion — изменение датчика движения
  • ChangedMagnet — изменение геркона (открытие/закрытие)
  • ChangedMagnetChange — изменение геркона с передачей изменений
  • ChangedPower — изменение состояния питания
  • ChangedPowerChange — изменение состояния питания с передачей изменений
  • ChangedPowerLoad — изменение нагрузки
  • ChangedAction — пользовательское действие
  • ChangedLeakChange — обнаружение протечки
  • ChangedInputChange — изменение входа
  • ChangedCounterChange — изменение счетчика
  • ChangedChannel — изменение канала
  • ChangedChannel0 — изменение канала 0
  • ChangedChannel1 — изменение канала 1

MQTT

  • MqttMessage — сообщение MQTT

Статистика

  • NewStats — новая статистика
  • NewStatsEx — расширенная статистика

Wi-Fi

  • WiFiConnect — подключение Wi-Fi
  • WiFiConnected — Wi-Fi подключен
  • WiFiNetworkFound — сеть Wi-Fi найдена

Приложение

  • ApplicationReady — приложение готово
  • ApplicationCreated — приложение создано
  • ApplicationGetDevices — получение устройств приложения
  • ApplicationDeviceCommand — команда устройству
  • ApplicationAddDeviceQueue — добавление устройства в очередь
  • ApplicationAddScanQueue — добавление сканирования в очередь
  • ApplicationDriverReady — драйвер готов
  • NewEvent — новое событие

База данных

  • DatabaseReady — БД готова
  • DatabaseConnected — БД подключена
  • DatabaseGetAllItems — получение всех элементов
  • DatabaseUpdateItem — обновление элемента
  • DatabaseUpdateDeviceParams — обновление параметров устройства
  • DatabaseCreateItem — создание элемента
  • DatabaseQuery — запрос к БД
  • DatabaseUpdate — обновление БД
  • DatabaseQueueQuery — запрос в очереди БД

Прочее

  • CheckSubDevice — проверка дочернего устройства
  • Publish — публикация
  • Notify — уведомление
  • UpdateSensor — обновление датчика
  • RemoveSensor — удаление датчика
  • UpdateSensorEx — расширенное обновление датчика

Примеры плагинов

Пример 1: Плагин для HTTP устройств

import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
import axios from 'axios';

class HttpDevicePlugin extends baseDriverModule {
  
  private baseUrl: string;

  initDeviceEx(resolve, reject) {
    super.initDeviceEx(() => {
      // Получаем URL из параметров
      this.baseUrl = this.params.url || 'http://192.168.1.100';
      this.app.log('HTTP устройство:', this.baseUrl);
      resolve({});
    }, reject);
  }

  connectEx(resolve, reject) {
    // Проверяем доступность устройства
    axios.get(`${this.baseUrl}/status`)
      .then(response => {
        this.app.log('Устройство онлайн');
        this.startPolling();
        resolve({});
      })
      .catch(error => {
        reject({message: 'Устройство недоступно'});
      });
  }

  startPolling() {
    setInterval(() => {
      axios.get(`${this.baseUrl}/sensors`)
        .then(response => {
          this.publishStatus(EventTypes.UpdateTemperature, {
            temperature_sensor: response.data.temperature,
            humidity_sensor: response.data.humidity,
            connected: true
          });
        })
        .catch(error => {
          this.app.error('Ошибка получения данных:', error.message);
        });
    }, 10000); // Каждые 10 секунд
  }

  commandEx(command, value, params, options, resolve, reject, status) {
    switch(command) {
      case 'on':
        axios.post(`${this.baseUrl}/control`, {power: 'on'})
          .then(() => resolve({state: 'on'}))
          .catch(error => reject({message: error.message}));
        break;
      
      case 'off':
        axios.post(`${this.baseUrl}/control`, {power: 'off'})
          .then(() => resolve({state: 'off'}))
          .catch(error => reject({message: error.message}));
        break;
      
      case 'status':
        axios.get(`${this.baseUrl}/status`)
          .then(response => resolve(response.data))
          .catch(error => reject({message: error.message}));
        break;
      
      default:
        reject({message: 'Неизвестная команда'});
    }
  }
}

const app = new HttpDevicePlugin();
app.logging = true;

Пример 2: Плагин с динамическими capabilities и миксинами

import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';
import {Params} from './mixins/params';

class DashboardPlugin extends baseDriverModule.with(Params) {
  
  appDevices = [];
  
  initDeviceEx(resolve, reject) {
    super.initDeviceEx(() => {
      this.app.log('Dashboard plugin initialized');
      this.eventName = this.eventTypeStatus(
        this.pluginTemplate.class_name,
        this.id
      );
      resolve({});
    }, reject);
  }
  
  connectEx(resolve, reject) {
    this.getDevices({currentStatus: true}).then(devices => {
      this.appDevices = devices;
      
      // Подписка на события устройств
      this.appDevices.forEach(device => {
        this.subscribeDevice(device.ident, 'changed_power');
      });
      
      // Динамическое создание capabilities
      this.updateCapabilities();
      this.updateSettingsEx();
      
      // Публикация capabilities и settings_ex
      this.publish(this.eventName, {
        capabilities: this.capabilities,
        settings_ex: this.settings_ex
      });
      
      resolve({});
    });
  }
  
  updateCapabilities() {
    this.capabilities = [];
    const deviceCount = this.params.device_count || 0;
    
    // Создание capabilities для каждого устройства
    for (let i = 0; i < deviceCount; i++) {
      const deviceIdent = this.params[`device_ident_${i}`];
      const deviceType = this.params[`device_type_${i}`];
      
      if (deviceIdent) {
        this.capabilities.push({
          ident: 'power',
          index: i + 1,
          display_name: `Device ${i + 1}`,
          options: {
            hideTitle: false,
            info: `info_${i}`,
            ignore_changes: false
          }
        });
      }
    }
  }
  
  updateSettingsEx() {
    this.settings_ex = [];
    const deviceCount = this.params.device_count || 4;
    
    // Заголовок
    this.settings_ex.push({
      key: 'devices_group',
      name: 'Devices Configuration',
      type: 'title'
    });
    
    // Создание настроек для каждого устройства
    for (let i = 0; i < deviceCount; i++) {
      // Тип устройства
      this.settings_ex.push({
        key: `device_type_${i}`,
        name: `Device ${i + 1} Type`,
        type: 'select',
        items: [
          {id: 'switch', title: 'Switch'},
          {id: 'sensor', title: 'Sensor'},
          {id: 'light', title: 'Light'}
        ],
        defaultValue: 'switch'
      });
      
      // Выбор устройства с фильтрацией
      this.settings_ex.push({
        key: `device_ident_${i}`,
        name: `Device ${i + 1}`,
        type: 'select',
        items: [],
        driver_support: ['supportPower'],
        multi: false
      });
    }
  }
  
  onSubscribeDevice(params: any) {
    // Обработка событий от подписанных устройств
    const device = this.appDevices.find(item => item.ident === params.ident);
    if (device) {
      device.currentStatus = params.currentStatus;
      this.app.log('Device status updated:', device.name, params.currentStatus);
      
      // Публикация обновленного статуса
      this.publishStatus(EventTypes.UpdatePower, {
        [`status_${device.ident}`]: params.currentStatus.power
      });
    }
  }
  
  commandEx(command, value, params, options, resolve, reject, status) {
    // Проверка прав администратора
    if (command === 'reset' && options && !options.is_admin) {
      return reject({message: 'Access Denied!'});
    }
    
    switch(command) {
      case 'update_config':
        // Обновление конфигурации
        this.updateCapabilities();
        this.updateSettingsEx();
        
        this.publish(this.eventName, {
          capabilities: this.capabilities,
          settings_ex: this.settings_ex
        });
        
        // Системное уведомление об обновлении настроек
        this.sendNotifyEx({
          system: true,
          type: 'settings-update',
          ident: this.ident
        });
        
        resolve({success: true});
        break;
      
      case 'status':
        resolve({
          connected: true,
          devices_count: this.capabilities.length
        });
        break;
      
      default:
        reject({message: 'Неизвестная команда'});
    }
  }
}

const app = new DashboardPlugin();
app.logging = true;

Пример 3: Плагин-шлюз с дочерними устройствами

import {baseDriverModule} from '../core/base-driver-module';
import {EventTypes} from '../enums/EventTypes';

class GatewayPlugin extends baseDriverModule {

  private devices: Map<string, any> = new Map();

  initDeviceEx(resolve, reject) {
    super.initDeviceEx(() => {
      this.app.log('Шлюз инициализирован');
      resolve({});
    }, reject);
  }

  connectEx(resolve, reject) {
    // Сканирование устройств
    this.scanDevices().then(() => {
      resolve({});
    });
  }

  async scanDevices() {
    // Обнаружение устройств (например, через Zigbee, Z-Wave и т.д.)
    const foundDevices = [
      {id: '0x123', type: 'light', name: 'Свет в гостиной'},
      {id: '0x456', type: 'sensor', name: 'Датчик температуры'}
    ];

    for (const device of foundDevices) {
      await this.registerDevice(device);
    }
  }

  async registerDevice(device: any) {
    let model, icon, capabilities;

    if (device.type === 'light') {
      model = 'gateway.light';
      icon = 'light';
      capabilities = [
        {
          property: 'state',
          ident: 'state',
          display_name: 'Включено',
          access: 'rw',
          homekit: true,
          yandex: true,
          options: {value_on: true, value_off: false}
        },
        {
          property: 'brightness',
          ident: 'brightness',
          display_name: 'Яркость',
          access: 'rw',
          homekit: true,
          options: {minValue: 0, maxValue: 100, stepValue: 1}
        }
      ];
    } else if (device.type === 'sensor') {
      model = 'gateway.temperature';
      icon = 'temp_sensor';
      capabilities = [
        {
          property: 'temperature',
          ident: 'temperature',
          display_name: 'Температура',
          access: 'r',
          scale: '°C'
        }
      ];
    }

    const result = await this.checkSubDevice(
      model,
      device.id,
      device.name,
      {icon, identifier: device.id, capabilities},
      null
    );

    this.devices.set(device.id, device);
    this.app.log('Зарегистрировано устройство:', device.name);
  }

  commandEx(command, value, params, options, resolve, reject, status) {
    const deviceId = options.ident;
    
    // Отправка команды конкретному дочернему устройству
    if (this.devices.has(deviceId)) {
      // Здесь отправка команды через протокол шлюза
      this.app.log('Команда для устройства', deviceId, ':', command, value);
      resolve({success: true});
    } else {
      reject({message: 'Устройство не найдено'});
    }
  }

  getSubDevicesEx(resolve, reject, zones) {
    // Возврат списка всех дочерних устройств
    const devices = Array.from(this.devices.values()).map(device => ({
      class_name: 'GatewayDevice',
      identifier: device.id,
      name: device.name,
      params: {
        icon: device.type === 'light' ? 'light' : 'temp_sensor',
        identifier: device.id
      }
    }));

    resolve(devices);
  }
}

const app = new GatewayPlugin();
app.logging = true;

Отладка плагинов

Локальный запуск

Для ручного тестирования плагина:

const app = new MyPlugin();
app.logging = true;

// Инициализация с тестовыми параметрами
app.initDevice({
  params: {
    url: 'http://192.168.1.100',
    port: 80
  }
}).then(() => {
  // Подключение
  app.connect({id: 1}).then(() => {
    // Тестовая команда
    app.command({
      command: 'status',
      id: 1
    }).then(result => {
      console.log('Результат:', result);
    });
  });
});

Логирование

// Разные уровни логирования
this.app.log('Информация');         // INFO
this.app.error('Ошибка');           // ERROR
this.app.debug('Отладка');          // DEBUG
this.app.info('Информация');        // INFO

// Логирование с автоматическим таймстампом и использованием памяти
// Формат: HH:mm:ss [memory] сообщение

Логи сохраняются в ${params.log_path}/${module}-${id}.log с ротацией:

  • Максимальный размер файла: 10 МБ
  • Количество файлов: 5

Параметры запуска

# Запуск с логированием
node dist/my-plugin.js 123 logging=true

# Удаленное подключение к BARY
node dist/my-plugin.js 123 host=192.168.1.100 port=8000

Лучшие практики

1. Обработка ошибок

commandEx(command, value, params, options, resolve, reject, status) {
  try {
    // Ваш код
    resolve(result);
  } catch (error) {
    this.app.error('Ошибка выполнения команды:', error.message);
    reject({message: error.message, ignore: false});
  }
}

2. Переподключение при ошибках

connectEx(resolve, reject) {
  const tryConnect = (attempts = 0) => {
    this.connectToDevice()
      .then(() => resolve({}))
      .catch(error => {
        if (attempts < 3) {
          this.app.log(`Попытка переподключения ${attempts + 1}/3`);
          setTimeout(() => tryConnect(attempts + 1), 5000);
        } else {
          reject({message: 'Не удалось подключиться'});
        }
      });
  };
  
  tryConnect();
}

3. Оптимизация публикации данных

// Используйте publishStatus для автоматической оптимизации
this.publishStatus(EventTypes.UpdateTemperature, {
  temperature: 22.5,  // Отправится только при изменении >10%
  humidity: 45        // или раз в 15 секунд
});

// Для важных событий используйте publish напрямую
this.publish(EventTypes.ChangedMotion, {
  motion: true  // Отправится сразу
});

4. Использование миксинов для модульности

// Разделяйте функциональность на миксины
// mixins/params.ts — работа с параметрами
// mixins/ipc.ts — дополнительная IPC логика
// mixins/servo.ts — управление сервоприводами

class MyPlugin extends baseDriverModule.with(Params, IPC, Servo) {
  // Чистый и модульный код
}

5. Управление зависимостями

В my-plugin.json указывайте только специфичные для вашего плагина зависимости:

{
  "dependencies": {
    "my-device-sdk": "^2.0.0",
    "zwave-js": "^9.2.2"
  }
}

BARY автоматически установит их при первом запуске плагина через RequireEx.

6. Использование очередей для последовательных операций

// Создайте очередь для команд устройству
const Queue = require('../lib/better-queue/queue');

this.requestQueue = new Queue((task, callback) => {
  this.deviceCommand(task.ident, task.command, {}, task.value)
    .then(() => callback(null, {}))
    .catch(error => callback(error));
}, {
  concurrent: 1,
  maxRetries: 3
});

// Используйте очередь вместо прямых вызовов
this.requestQueue.push({
  ident: device.ident,
  command: 'set_power',
  value: true
});

Сборка и развертывание

Компиляция

# Установка зависимостей
npm install

# Сборка
npm run build

# Отладка с автоперезагрузкой
npm run debug

Поддержка и документация